// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jhkim

#pragma once

#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#include "coin2/exchange/base/order/rate_tracker.h"
#include "coin2/exchange/base/order/processor.h"
#include "coin2/exchange/sim/order_v1/backtesting_matcher.h"
#include "coin2/strategy/linear_model/product_strategy.h"
#include "coin2/support/order_latency/model.h"
#include "presto/quant/symbology/symbol_map.h"

namespace coin2::exchange::sim::order_v1 {

using coin::proto::OrderDirection;
using coin::proto::OrderDuration;
using coin::proto::OrderEvent;
using coin::proto::OrderType;
using coin::proto::TradeSide;
using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::feed::timer::FeedTimer;
using coin2::exchange::base::order::IOrderSubscriber;
using coin2::exchange::base::order::OrderContext;
using coin2::exchange::base::order::OrderContextManager;
using coin2::exchange::base::order::OrderUpdateManager;
using coin2::exchange::base::order::SymbologyCache;
using coin2::exchange::order::AccountSimConfig;
using coin2::exchange::order::OrderRecordProto;
using coin2::exchange::order::SimConfig2;
using coin2::exchange::symbology::ProductRequest;
using ::presto::Symbol;
using ::presto::SymbolManager;
using PrivateSubscriberConfig = coin2::exchange::order::PrivateSubscriberConfig;
using coin2::exchange::base::market::MarketType;
using coin2::exchange::order::OrderSystemConfig;

struct OrderStateManager {
  OrderStateManager(const std::string& name, const MarketExchangeApi& mea)
      : oum(name, mea), ocm(mea, 4096) {}

  void InitOrderEventFromResponse(
      const FeedUpdate& upd,
      const OrderRecordProto& order_record,
      OrderEvent* oe) {
    oe->set_native_symbol(upd.product().native_symbol());
    oe->set_symbol(upd.product().absolute_norm());
    oe->set_exchange_type(upd.exchange());
    oe->set_market_type(upd.market());
    oe->set_type(OrderEvent::ORDER_FILLED);
    oe->set_event_time(order_record.time());
    oe->set_internal_order_id(fmt::to_string(order_record.order_id()));
    oe->set_proc_order_id(order_record.order_id());
    if (order_record.sign() == 1) {
      oe->set_trade_side(TradeSide::TRADE_BUY_SIDE);
    } else if (order_record.sign() == -1) {
      oe->set_trade_side(TradeSide::TRADE_SELL_SIDE);
    } else {
      THROW();
    }
    oe->set_fill_price(order_record.fill_price());
    oe->set_fill_qty(order_record.fill_qty());
    oe->set_order_price(order_record.order_price());
    oe->set_order_qty(order_record.order_qty());
    oe->set_publish_live(true);
    oe->set_duplicate(false);
  }

  void UpdateEachRecord(
      const FeedUpdate& update,
      const OrderRecordProto& order_record,
      int64_t sim_account_id) {
    switch (order_record.type()) {
      case OrderRecordProto::ORDER_ACCEPTED: {
        auto* oc = ocm.FindByProcOrderId(order_record.order_id());
        if (oc) {
          oc->HandleOrderAccepted(order_record.time(), fmt::to_string(order_record.order_id()));
        }
        OrderEvent oe;
        InitOrderEventFromResponse(update, order_record, &oe);
        oe.set_type(OrderEvent::ORDER_ACCEPTED);
        oe.clear_fill_price();
        oe.clear_fill_qty();
        oe.set_tag(oc->tag());
        oum.GetOrderUpdateMutable()->SetTimestamp(order_record.time());
        oum.GetOrderUpdateMutable()->sim_account_id = sim_account_id;
        oum.GetProductOrderInfoMutable(update.product().native_symbol())->UpdateOrderState(oe, oc);
        CHECK_EQ(oum.update().product().native_symbol(), update.product().native_symbol());
        oum.PublishAccountOrder(oe);
        auto oeph = ocm.GenerateOrderEventFlusher();
      } break;
      case OrderRecordProto::ORDER_ERROR:
        ocm.SetDead(order_record.order_id());
        break;
      case OrderRecordProto::CANCEL_ERROR:
        LOG(INFO) << fmt::format("Cancel error order_id {}", order_record.order_id());
        break;
      case OrderRecordProto::CANCEL_CONFIRMED:
        ocm.SetDead(order_record.order_id());
        if (cum_fill_qtys_.count(order_record.order_id()) > 0) {
          cum_fill_qtys_.erase(cum_fill_qtys_.find(order_record.order_id()));
        }
        break;
      case OrderRecordProto::ORDER_FILLED: {
        cum_fill_qtys_[order_record.order_id()] += order_record.fill_qty();
        const double eps_qty = order_record.order_qty() * 1e-8;

        OrderEvent oe;
        if (cum_fill_qtys_.at(order_record.order_id()) >= order_record.order_qty() - eps_qty) {
          ocm.SetDead(order_record.order_id());
          oe.set_fully_filled(true);
          cum_fill_qtys_.erase(cum_fill_qtys_.find(order_record.order_id()));
        }

        InitOrderEventFromResponse(update, order_record, &oe);
        const auto* oc = oum.order_manager()->FindByProcOrderId(order_record.order_id());
        oe.set_tag(oc->tag());
        oum.GetOrderUpdateMutable()->SetTimestamp(order_record.time());
        oum.GetOrderUpdateMutable()->sim_account_id = sim_account_id;
        oum.GetProductOrderInfoMutable(update.product().native_symbol())->UpdateOrderState(oe, oc);
        CHECK_EQ(oum.update().product().native_symbol(), update.product().native_symbol());
        oum.PublishAccountOrder(oe);
      } break;
      default: {
      };
    }
  }

  void UpdatePosition(const BacktestingMatcher* matcher, const Symbol* symbol) {
    double mtm_price = matcher->GetMtmPrice();
    double diff_curr_pos = (matcher->GetNetPosition() * mtm_price) - curr_pos_quote_[symbol];
    double diff_buy_working = (matcher->GetWorkingQty(1) * mtm_price) - buy_working_quote_[symbol];
    curr_pos_quote_[symbol] = matcher->GetNetPosition() * mtm_price;
    buy_working_quote_[symbol] = matcher->GetWorkingQty(1) * mtm_price;
    pos_quote += diff_curr_pos;
    working_quote += diff_buy_working;
  }

  OrderUpdateManager oum;
  OrderContextManager ocm;
  double pos_quote{0};
  double working_quote{0};

  std::optional<double> max_position_quote;
  std::unordered_map<int64_t, double> cum_fill_qtys_;
  std::unordered_map<const Symbol*, double> curr_pos_quote_;
  std::unordered_map<const Symbol*, double> buy_working_quote_;
};

class BacktestingGateway : public coin2::strategy::linear_model::FeatureFeedConverter,
                           public coin2::exchange::base::order::BaseOrderGateway,
                           public coin2::exchange::base::order::BaseOrderProcessor,
                           public coin2::exchange::base::feed::IFeedSubscriber {
 public:
  BacktestingGateway(
      const std::string& name,
      const MarketExchangeApi& mea,
      const OrderSystemConfig& oss_config,
      const SimConfig2& config,
      const PrivateSubscriberConfig& subscriber_config,
      std::vector<const IProduct*>* products,
      SymbologyCache* symcache,
      IOrderSubscriber* callback);
  void set_latency_recorder(LatencyRecorder* latency_recorder) override {}

  // return internal order id
  bool CheckRateLimit(ActionType action_type);
  void UpdateTimeForSim(int64_t ts) final { last_time_ = ts; }
  std::optional<int64_t> SubmitOrder(const coin2::exchange::base::order::OrderSpec& spec) final {
    CHECK_EQ(sim_account_ids_.size(), 1);
    CHECK_EQ(sim_account_ids_.count(0L), 1);
    // 0, thus default account id.
    return SubmitOrderForSim(0L, spec);
  }
  std::optional<int64_t> SubmitOrderForSim(
      int64_t account_id,
      const coin2::exchange::base::order::OrderSpec& spec) final;
  bool CancelOrder(int64_t proc_order_id) override;
  bool CancelOrderAll(const std::string& symbol) override;
  std::optional<int64_t> AmendOrder(int64_t proc_order_id, double amend_price, double amend_qty)
      final;

  const Symbol* GetSymbol(const MarketExchangeApi& mea, const IProduct& product);
  const Symbol* GetSymbol(const FeedUpdate& upd) override {
    return GetSymbol(upd.mea(), upd.product());
  }

  const OrderContextManager& order_manager() const final { return osm_.at(0L)->ocm; }
  OrderContextManager* mutable_order_manager() final { return &osm_.at(0L)->ocm; }
  const OrderUpdateManager& upd_mgr() const final { return osm_.at(0L)->oum; }
  OrderUpdateManager* mutable_upd_mgr() final { return &osm_.at(0L)->oum; }
  coin2::exchange::base::order::OrderQuerier* GenOrderQuerier(int64_t sim_account_id) {
    auto* osm = osm_.at(sim_account_id).get();
    return new coin2::exchange::base::order::OrderQuerier(
        osm->oum,
        osm->ocm,
        true);  // is_suporting_separate_pos
  }

  void UpdateAccountInfo(int64_t ts, const Symbol* symbol);
  void onBookFeed(const FeedUpdate& update) override;
  void onTradeFeed(const FeedUpdate& update) override;
  void onLiquidationFeed(const FeedUpdate& update) override {}
  void onOpenInterestFeed(const FeedUpdate& update) override {}
  void onMarkPriceFeed(const FeedUpdate& update) override {}
  void onIndexFeed(const FeedUpdate& update) override {}
  void onFundingRateFeed(const FeedUpdate& update) override {}
  void onIvGreeksFeed(const FeedUpdate& update) override {}
  void onNavFeed(const FeedUpdate& update) override {}
  void onKlineFeed(const FeedUpdate& update) override {}
  void onWalletFeed(const FeedUpdate&) override {}
  void onMarketAnalysisFeed(const FeedUpdate&) override {}
  void onTopLongShortAccountRatioFeed(const FeedUpdate& upd) {};
  void onTopLongShortPositionRatioFeed(const FeedUpdate& upd) {};
  void onGlobalLongShortAccountRatioFeed(const FeedUpdate& upd) {};
  void onTakerBuySellRatioFeed(const FeedUpdate& upd) {};
  void onOpenInterestHistFeed(const FeedUpdate& upd) {};
  void onHeartbeatFeed(const FeedTimer& timer) override;
  const MarketExchangeApi& mea() const { return mea_; }
  void CheckAndUpdateOrderSpec(const coin2::exchange::base::order::OrderSpec& spec);
  coin2::exchange::base::order::IRateTracker* GetMutableRateTracker() override {
    return rate_tracker_.get();
  }
  void SetApiRateLimitCheckForSim(bool check) final {
    enable_api_rate_limit_check_ = check;
  }

 private:
  MarketExchangeApi mea_;
  std::vector<std::unique_ptr<IProduct>> products_;
  std::unordered_set<int64_t> sim_account_ids_;
  std::vector<std::unique_ptr<BacktestingMatcher>> matchers_;
  std::map<std::pair<int64_t, const Symbol*>, BacktestingMatcher*> matchers_map_;
  std::unordered_map<const Symbol*, std::vector<BacktestingMatcher*>> feeders_;

  SymbolManager symbol_manager_;
  ::coin2::exchange::base::symbology::ProductEncyclopedia product_cache_;
  std::unordered_map<std::string, std::string> base_currency_map_;

  int64_t ioid_ = 0L;
  std::unordered_map<int64_t, std::pair<BacktestingMatcher*, int64_t>> ioid_map_;
  std::unordered_map<int64_t, int64_t> last_account_info_update_ts_;
  PrivateSubscriberConfig subscriber_config_;
  int64_t account_info_update_interval_{0};
  std::optional<int64_t> last_time_;
  std::optional<int64_t> last_feed_ts_;

  std::unordered_map<int64_t, std::unique_ptr<OrderStateManager>> osm_;

  std::unordered_map<const Symbol*, std::string> symbol2product_str_;

  bool has_heartbeat_ = false;
  std::unordered_map<const Symbol*, const IProduct*> stored_products_;
  std::unordered_map<const Symbol*, std::unique_ptr<FeedUpdate>> stored_books_;
  std::optional<int64_t> exchange_timestamp_override_;
  bool first_write_ = true;
  std::unique_ptr<coin2::exchange::base::order::IRateTracker> rate_tracker_ = {};
  bool enable_api_rate_limit_check_ = true;
};  // namespace coin2::exchange::sim::order_v1

}  // namespace coin2::exchange::sim::order_v1
